/*
 * Copyright (C) 2025 Tsingteng MicroSystem
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/**
 * @file t4t_api.c
 * @brief T4T操作API实现
 */

#include "t4t_api.h"
#include "nci_utils.h"

#if IS_FEATURE_ON(NCI_FEATURE_RW_T4T) // TODO: || IS_FEATURE_ON(NCI_FEATURE_T4T_NFCEE)

/**
 * @brief 重置T4T目标的状态
 *
 * @note 不会重置transmit函数
 *
 * @param[in] target 需要重置的目标
 *
 * @return 重置成功返回NCI_STATUS_OK, 否则返回对应错误码
 */
tNCI_STATUS t4t_reset(tT4T_TARGET *target) {
    OSI_CHK_PARAM_INVALID(!target, return NCI_STATUS_EXT_INVALID_PARAM);
    target->curr_selected_file_id = 0;
    target->last_sw = 0;
    return NCI_STATUS_OK;
}
/**
 * @brief 发送原始APDU指令
 *
 * @param[in] target 指令发送的目标
 * @param[in] p_apdu 需要发送的APDU指令
 * @param[out] pp_rsp 响应数据
 *
 * @return 发送指令成功返回NCI_STATUS_OK, 否则返回对应错误码
 */
tNCI_STATUS t4t_send_apdu(tT4T_TARGET *target, tT4T_APDU *p_apdu, tNCI_BUF **pp_rsp) {
    OSI_CHK_PARAM_INVALID(!target, return NCI_STATUS_EXT_INVALID_PARAM);
    OSI_CHK_PARAM_INVALID(!p_apdu, return NCI_STATUS_EXT_INVALID_PARAM);
    target->last_sw = 0;
    tNCI_STATUS ret = NCI_STATUS_EXT_FAILED;
    tNCI_BUF *cmd = alloc_nci_buf(0);
    tNCI_BUF *rsp = pp_rsp ? *pp_rsp : NULL;
    if (rsp) {
        reset_nci_buf(rsp);
    } else {
        rsp = alloc_nci_buf(0);
    }
    if (!cmd || !rsp) {
        NFCLOG_E("alloc t4t buf failed");
        goto cleanup_rsp;
    }
    UINT8_TO_BUF(cmd, p_apdu->cla);
    UINT8_TO_BUF(cmd, p_apdu->ins);
    UINT8_TO_BUF(cmd, p_apdu->p1);
    UINT8_TO_BUF(cmd, p_apdu->p2);

    if (p_apdu->p_dat) {
        if (p_apdu->lc > 0xFF) {
            UINT8_TO_BUF(cmd, 0x00);
            UINT8_TO_BUF(cmd, (p_apdu->lc >> 8) & 0xFF);
        }
        UINT8_TO_BUF(cmd, p_apdu->lc & 0xFF);
        ARRAY_TO_BUF(cmd, p_apdu->p_dat, p_apdu->lc);
    }
    if (p_apdu->le_present) {
        if (p_apdu->le > 0xFF) {
            if (!p_apdu->p_dat) {
                UINT8_TO_BUF(cmd, 0x00);
            }
            UINT8_TO_BUF(cmd, (p_apdu->le >> 8) & 0xFF);
        }
        UINT8_TO_BUF(cmd, p_apdu->le & 0xFF);
    }
    ret = target->transmit(cmd, rsp);
    if (ret != NCI_STATUS_OK) {
        NFCLOG_E("t4t send apdu failed, ret=%d", ret);
        goto cleanup_rsp;
    }
    if (rsp->len < T4T_SW_LEN) {
        NFCLOG_E("t4t send apdu failed, rsp len=%d", rsp->len);
        ret = NCI_STATUS_EXT_FAILED;
        goto cleanup_rsp;
    }
    uint16_t sw = (rsp->data[rsp->len - 2] << 8) | rsp->data[rsp->len - 1];
    NFCLOG_I("sw=%04X, len=%d", sw, rsp->len);
    target->last_sw = sw;
    if (!p_apdu->skip_verify_sw && sw != T4T_SW_OK) {
        NFCLOG_E("t4t send apdu failed, sw=0x%04X", sw);
        ret = NCI_STATUS_EXT_APDU_STATUS_ERROR;
        goto cleanup_rsp;
    }
    if (pp_rsp) {
        // 调用方接收RSP，直接返回
        *pp_rsp = rsp;
        goto cleanup_cmd;
    } else {
        // 调用方不接收RSP，直接在此处释放rsp
        goto cleanup_rsp;
    }
cleanup_rsp:
    // 释放rsp
    free_nci_buf(rsp);
    if (pp_rsp) {
        *pp_rsp = NULL;
    }
cleanup_cmd:
    // 释放cmd
    free_nci_buf(cmd);
    return ret;
}

tNCI_STATUS t4t_select_application(tT4T_TARGET *target, const uint8_t *p_aid, uint16_t aid_len, tNCI_BUF **pp_rsp) {
    OSI_CHK_PARAM_INVALID(!target, return NCI_STATUS_EXT_INVALID_PARAM);
    tT4T_APDU apdu = {
        .cla = T4T_CLA_DEFAULT,
        .ins = T4T_INS_SELECT,
        .p1 = T4T_P1_SELECT_BY_NAME,
        .p2 = T4T_P2_FIRST_OR_ONLY_00,
        .lc = aid_len,
        .p_dat = p_aid,
        .le_present = true,
        .le = 0x00,
    };
    target->curr_selected_file_id = 0;
    return t4t_send_apdu(target, &apdu, pp_rsp);
}

tNCI_STATUS t4t_select_file(tT4T_TARGET *target, uint16_t file_id, uint8_t p2, tNCI_BUF **pp_rsp) {
    OSI_CHK_PARAM_INVALID(!target, return NCI_STATUS_EXT_INVALID_PARAM);
    if (file_id == target->curr_selected_file_id) {
        return NCI_STATUS_OK;
    }
    uint8_t p_file_id[T4T_FILE_ID_LEN] = {
        (file_id >> 8) & 0xFF,
        file_id & 0xFF
    };
    tT4T_APDU apdu = {
        .cla = T4T_CLA_DEFAULT,
        .ins = T4T_INS_SELECT,
        .p1 = T4T_P1_SELECT_BY_FILE,
        .p2 = p2,
        .lc = T4T_FILE_ID_LEN,
        .p_dat = p_file_id,
        .le_present = false,
    };
    tNCI_STATUS ret = t4t_send_apdu(target, &apdu, pp_rsp);
    if (ret == NCI_STATUS_OK) {
        target->curr_selected_file_id = file_id;
    }
    return ret;
}


tNCI_STATUS t4t_read_binary(tT4T_TARGET *target, uint16_t offset, uint16_t read_len, bool check_len, tNCI_BUF **pp_rsp) {
    OSI_CHK_PARAM_INVALID(!target, return NCI_STATUS_EXT_INVALID_PARAM);
    tT4T_APDU apdu = {
        .cla = T4T_CLA_DEFAULT,
        .ins = T4T_INS_READ_BINARY,
        .p1 = (offset >> 8) & 0xFF,
        .p2 = offset & 0xFF,
        .le_present = true,
        .le = read_len,
    };
    tNCI_STATUS ret = t4t_send_apdu(target, &apdu, pp_rsp);
    if (ret != NCI_STATUS_OK) {
        NFCLOG_E("t4t read binary failed, ret=%d", ret);
        return ret;
    }
    tNCI_BUF *rsp = *pp_rsp;
    if (check_len && rsp->len != (read_len + T4T_SW_LEN)) {
        NFCLOG_E("t4t read binary failed, rsp len=%d", rsp->len);
        return NCI_STATUS_EXT_FAILED;
    }
    return ret;
}

tNCI_STATUS t4t_read_binary_odo(tT4T_TARGET *target, uint32_t offset, uint16_t max_le, tNCI_BUF **pp_rsp, uint16_t *p_rsp_offset) {
    OSI_CHK_PARAM_INVALID(!target, return NCI_STATUS_EXT_INVALID_PARAM);
    OSI_CHK_PARAM_INVALID(!p_rsp_offset, return NCI_STATUS_EXT_INVALID_PARAM);
    uint8_t data[] = {0x54, 0x03, (offset >> 16) & 0xFF, (offset >> 8) & 0xFF, offset & 0xFF};
    tT4T_APDU apdu = {
        .cla = T4T_CLA_DEFAULT,
        .ins = T4T_INS_READ_BINARY_ODO,
        .p1 = 0,
        .p2 = 0,
        .lc = sizeof(data),
        .p_dat = data,
        .le_present = true,
        .le = 0,
    };
    tNCI_STATUS ret = t4t_send_apdu(target, &apdu, pp_rsp);
    if (ret != NCI_STATUS_OK) {
        NFCLOG_E("t4t read binary failed, ret=%d", ret);
        return ret;
    }
    tNCI_BUF *rsp = *pp_rsp;
    uint8_t *p = rsp->data;
    uint8_t rsp_offset = 0;
    if (rsp->len < 2) {
        NFCLOG_E("t4t read binary failed, rsp len=%d", rsp->len);
        return NCI_STATUS_EXT_FAILED;
    }
    if (p[0] != 0x53) {
        NFCLOG_E("invalid odo tag, tag=%02X", p[0]);
        return NCI_STATUS_EXT_FAILED;
    }
    if (p[1] <= 0x7F) {
        rsp_offset += 2;
    } else if (p[1] == 0x81) {
        if (rsp->len < 3) {
            NFCLOG_E("invalid odo length, rsp len=%d", rsp->len);
            return NCI_STATUS_EXT_FAILED;
        }
        if (p[2] <= 0xFD) {
            rsp_offset += 3;
        } else {
            NFCLOG_E("invalid odo length, length=%02X", p[2]);
            return NCI_STATUS_EXT_FAILED;
        }
    } else if (p[1] == 0x82) {
        if (rsp->len < 4) {
            NFCLOG_E("invalid odo length, rsp len=%d", rsp->len);
            return NCI_STATUS_EXT_FAILED;
        }
        uint16_t r_apdu_len = (uint16_t)(p[2] << 8) | p[3];
        if (r_apdu_len <= (max_le - 4)) {
            rsp_offset += 4;
        } else {
            NFCLOG_E("invalid odo length, length=%04X", r_apdu_len);
            return NCI_STATUS_EXT_FAILED;
        }
    } else {
        NFCLOG_E("invalid odo length, length=%02X", p[1]);
        return NCI_STATUS_EXT_FAILED;
    }
    *p_rsp_offset = rsp_offset;
    return ret;
}

tNCI_STATUS t4t_write_binary(tT4T_TARGET *target, uint16_t offset, uint16_t data_len, const uint8_t *p_dat, tNCI_BUF **pp_rsp) {
    OSI_CHK_PARAM_INVALID(!target, return NCI_STATUS_EXT_INVALID_PARAM);
    OSI_CHK_PARAM_INVALID(data_len == 0, return NCI_STATUS_EXT_INVALID_PARAM);
    OSI_CHK_PARAM_INVALID(!p_dat, return NCI_STATUS_EXT_INVALID_PARAM);
    tT4T_APDU apdu = {
        .cla = T4T_CLA_DEFAULT,
        .ins = T4T_INS_UPDATE_BINARY,
        .p1 = (offset >> 8) & 0xFF,
        .p2 = offset & 0xFF,
        .lc = data_len,
        .p_dat = p_dat,
        .le_present = false,
    };
    return t4t_send_apdu(target, &apdu, pp_rsp);
}

tNCI_STATUS t4t_write_binary_odo(tT4T_TARGET *target, uint32_t offset, const uint8_t *p_dat, uint16_t max_lc, tNCI_BUF **pp_rsp, uint16_t *p_data_len) {
    OSI_CHK_PARAM_INVALID(!target, return NCI_STATUS_EXT_INVALID_PARAM);
    OSI_CHK_PARAM_INVALID(!p_dat, return NCI_STATUS_EXT_INVALID_PARAM);
    OSI_CHK_PARAM_INVALID(!p_data_len, return NCI_STATUS_EXT_INVALID_PARAM);
    uint16_t data_len = *p_data_len;
    OSI_CHK_PARAM_INVALID(data_len == 0, return NCI_STATUS_EXT_INVALID_PARAM);
    tNCI_STATUS ret = NCI_STATUS_EXT_FAILED;
    uint8_t length_size = 0;
    if (data_len <= 0x7F) {
        length_size = T4T_BER_TLV_LENGTH_1_BYTE;
    } else if ((data_len + T4T_ODO_HEADER_2BYTES_LENGTH) <= 0xFF) {
        length_size = T4T_BER_TLV_LENGTH_2_BYTE;
    } else {
        if (max_lc > 0xFF) {
            if (data_len <= 0xFF) {
                length_size = T4T_BER_TLV_LENGTH_2_BYTE;
            } else {
                length_size = T4T_BER_TLV_LENGTH_3_BYTE;
            }
        } else {
            length_size = T4T_BER_TLV_LENGTH_2_BYTE;
        }
    }
    uint32_t write_len = data_len + 6 + length_size;
    if (write_len > max_lc) {
        write_len = max_lc;
    }
    uint8_t *data = osi_calloc(data_len + 6 + length_size);
    if (!data) {
        NFCLOG_E("calloc data failed");
        ret = NCI_STATUS_EXT_ALLOC_FAILED;
        goto cleanup;
    }
    uint8_t *p = data;
    UINT8_TO_STREAM(p, 0x54);
    UINT8_TO_STREAM(p, 0x03);
    UINT24_TO_STREAM_BE(p, offset);
    UINT8_TO_STREAM(p, 0x53);
    if (length_size == T4T_BER_TLV_LENGTH_1_BYTE) {
        UINT8_TO_STREAM(p, data_len);
    } else if (length_size == T4T_BER_TLV_LENGTH_2_BYTE) {
        UINT8_TO_STREAM(p, 0x81);
        UINT8_TO_STREAM(p, data_len);
    } else if (length_size == T4T_BER_TLV_LENGTH_3_BYTE && data_len <= 0xFFFF) {
        UINT8_TO_STREAM(p, 0x82);
        UINT16_TO_STREAM_BE(p, data_len);
    } else {
        NFCLOG_E("data_len is too large, data_len=%d", data_len);
        ret = NCI_STATUS_EXT_INVALID_PARAM;
        goto cleanup;
    }
    if (!osi_memcpy_s(p, data_len, p_dat, data_len)) {
        NFCLOG_E("memcpy data failed");
        ret = NCI_STATUS_EXT_COPY_FAILED;
        goto cleanup;
    }
    tT4T_APDU apdu = {
        .cla = T4T_CLA_DEFAULT,
        .ins = T4T_INS_UPDATE_BINARY_ODO,
        .p1 = 0,
        .p2 = 0,
        .lc = write_len,
        .p_dat = data,
        .le_present = false,
    };
    ret = t4t_send_apdu(target, &apdu, pp_rsp);
cleanup:
    if (data) {
        osi_free(data);
    }
    if (ret == NCI_STATUS_OK) {
        *p_data_len = write_len - 6 - length_size;
        NFCLOG_I("*p_data_len=%d", *p_data_len);
    }
    return ret;
}

#endif
